package ollama

import (
	"context"
	"io"
	"net/http"
	"net/url"
	"os"
	"os/exec"

	"gitee.com/linxing_3/sr-core/plugins/storage/cache/file"
	"gitee.com/linxing_3/sr-core/yerr"
	"github.com/ollama/ollama/api"
	"github.com/ollama/ollama/envconfig"
	"github.com/spf13/cast"
)

func New(opts ...Options) (*Ollama, error) {
	opt := &option{
		Url:           "http://localhost:11434",
		Path:          "./ollama",
		CacheFilePath: "./ollama_cache",
		ctx:           context.Background(),
	}
	for _, o := range opts {
		o(opt)
	}

	url, err := url.Parse(opt.Url)
	if err != nil {
		return nil, err
	}

	client := api.NewClient(url, http.DefaultClient)
	cache := file.NewFileCache(file.WithRootDir(opt.CacheFilePath), file.WithContext(opt.ctx))

	cli := &Ollama{
		Client: client,
		cache:  cache,
		envCfg: &EnvCfg{cache: cache},
		opt:    opt,
		close:  make(chan bool, 1),
	}
	cli.envCfg.Init()

	cli.setBinPath(opt.Path)
	// go cli.readStdOut()
	return cli, nil
}

type Ollama struct {
	opt    *option
	envCfg *EnvCfg
	*api.Client

	cache   *file.File
	options map[string]envconfig.EnvVar
	binCli  *exec.Cmd

	stdOut io.Reader
	close  chan bool
}

func (o *Ollama) Start(ctx context.Context) error {
	if o.binCli.Process != nil && o.binCli.ProcessState != nil {
		o.binCli.Process.Kill()
	}
	if err := o.binCli.Start(); err != nil {
		return yerr.ErrBizNotSupport.Errorf("Ollama start failed: %w", err)
	}

	if o.binCli.Process.Pid == 0 {
		return yerr.ErrBizNotSupport.Errorf("Ollama start failed: %w", os.ErrProcessDone)
	}

	// out, err := o.binCli.CombinedOutput()
	// fmt.Println("Ollama start: ",o.binCli.Process., " Err: ", err)
	// o.stdOut, _ = o.binCli.StdoutPipe()

	return nil
}

func (o *Ollama) readStdOut() {
	for {
		select {
		case <-o.close:
			return
		default:
			// read
			if o.stdOut != nil {
				io.Copy(os.Stdout, o.stdOut)
			}
		}
	}
}

func (o *Ollama) Stop(ctx context.Context) error {
	if o.binCli == nil {
		return nil
	}
	if o.binCli.Process != nil && o.binCli.ProcessState != nil {
		o.binCli.Process.Kill()
	} else {
		// find pid

	}
	return nil
}

func (o *Ollama) GetServerPort() string {
	return envconfig.Host().Port()
}

func (o *Ollama) setBinPath(path string) error {
	o.Stop(context.Background())
	if stat, err := os.Stat(path); err != nil || stat.IsDir() {
		o.binCli = exec.CommandContext(o.opt.ctx, "ollama", "serve")
	} else {
		o.binCli = exec.CommandContext(o.opt.ctx, path, "serve")
	}
	o.envCfg.OLLAMA_DEBUG = true
	for k, v := range o.envCfg.asMap() {
		o.binCli.Env = append(o.binCli.Env, k+"="+cast.ToString(v))
	}
	return o.envCfg.Flush(context.Background())
}
